/**********************************************************************
Copyright (c) 2006 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. 


Contributors:
    ...
**********************************************************************/
package org.datanucleus.store.valuegenerator;

import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.store.StoreManager;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;

/**
 * Abstract value generator.
 */
public abstract class AbstractGenerator<T> implements ValueGenerator<T>
{
    protected StoreManager storeMgr;

    /** Symbolic name for the value generator. */
    protected String name;

    /** Allocation size */
    protected int allocationSize = 5;

    /** Initial value (of the first id). */
    protected int initialValue = 0;

    /** The current block of values that have been reserved. */
    protected ValueGenerationBlock<T> block;

    /**
     * Constructor.
     * @param storeMgr Store Manager
     * @param name Symbolic name for this generator
     */
    public AbstractGenerator(StoreManager storeMgr, String name)
    {
        this.storeMgr = storeMgr;
        this.name = name;
    }

    /**
     * Accessor for the symbolic name for this generator.
     * @return Symbolic name for the generator.
     */
    public String getName()
    {
        return name;
    }

    /**
     * Get next value from the reserved block of values.
     * @return The next value
     */
    public synchronized T next()
    {
        // If the current block of ids is null or empty get a new one
        if (block == null || !block.hasNext())
        {
            // No more elements left in the block so replace it with a new one
            block = obtainGenerationBlock();
        }

        return block.next();
    }

    /**
     * Accessor for the current value allocated.
     * Returns null if none are allocated
     * @return The current value
     */
    public synchronized T current()
    {
        if (block == null)
        {
            return null;
        }
        return block.current();
    }

    /**
     * Accessor for the next element as a long.
     * @return The next element
     * @throws NucleusDataStoreException Thrown if not numeric
     */
    public long nextValue()
    {
        return getLongValueForObject(next());
    }

    /**
     * Accessor for the current element as a long.
     * @return The current element
     * @throws NucleusDataStoreException Thrown if not numeric
     */
    public long currentValue()
    {
        return getLongValueForObject(current());
    }

    /**
     * Convenience method to convert a value into a long.
     * Throws NucleusDataStoreException if the value is not numeric.
     * @param oid The id
     * @return The long value
     * @throws NucleusDataStoreException Thrown if not numeric
     */
    private long getLongValueForObject(Object oid)
    {
        if (oid instanceof Long)
        {
            return ((Long)oid).longValue();
        }
        else if (oid instanceof Integer)
        {
            return ((Integer)oid).longValue();
        }
        else if (oid instanceof Short)
        {
            return ((Short)oid).longValue();
        }

        throw new NucleusDataStoreException(Localiser.msg("040009", name));
    }

    /**
     * Method to allocate a number of values into the block.
     * If the block already exists and has remaining values, the additional values are added to the block.
     * @param additional The number to allocate
     */
    public synchronized void allocate(int additional)
    {
        if (block == null)
        {
            // No existing block so replace the existing block
            block = obtainGenerationBlock(additional);
        }
        else
        {
            // Existing block so append to it
            block.addBlock(obtainGenerationBlock(additional));
        }
    }

    /**
     * Get a new block with the default number of ids.
     * @return the block
     */
    protected ValueGenerationBlock<T> obtainGenerationBlock()
    {
        // -1 here implies just use the default reserveBlock on the generator
        return obtainGenerationBlock(-1);
    }

    /**
     * Get a new block with the specified number of ids.
     * @param number The number of additional ids required
     * @return the block
     */
    protected ValueGenerationBlock<T> obtainGenerationBlock(int number)
    {
        ValueGenerationBlock<T> block = null;

        // Try getting the block
        try
        {
            try
            {
                if (number < 0)
                {
                    block = reserveBlock();
                }
                else
                {
                    block = reserveBlock(number);
                }
            }
            catch (ValueGenerationException vex)
            {
                // attempt to obtain the block of unique identifiers is invalid
                NucleusLogger.VALUEGENERATION.info(Localiser.msg("040003", vex.getMessage()));
                throw vex;
            }
            catch (RuntimeException ex)
            {
                // attempt to obtain the block of unique identifiers is invalid
                NucleusLogger.VALUEGENERATION.info(Localiser.msg("040003", ex.getMessage()));
                throw ex;
            }
        }
        finally
        {
        }

        return block;
    }

    /**
     * Method to reserve a default sized block of values.
     * @return The reserved block
     */
    protected ValueGenerationBlock<T> reserveBlock()
    {
        return reserveBlock(allocationSize);
    }

    /**
     * Method to reserve a block of "size" values.
     * @param size Number of values to reserve
     * @return The allocated block
     */
    protected abstract ValueGenerationBlock<T> reserveBlock(long size);
}